Guild icon
Project Sekai
πŸ”’ CrewCTF 2023 / βœ…-web-archive_stat_viewer
Avatar
archive_stat_viewer - 1000 points
Category: Web Description: > Warning: Never extract archives from untrusted sources without prior inspection. It is possible that files are created outside of path, e.g. members that have absolute filenames starting with "/" or filenames with two dots "..". I'm aware this warning but didn't know what to do right. Is this okay? Author: Satoooon http://archive-stat-viewer.chal.crewc.tf:8081/ Files:Tags: No tags.
Sutx pinned a message to this channel. 07/08/2023 10:01 AM
Avatar
@ElleuchX1 wants to collaborate 🀝
Avatar
hmm starting the file with ./ maybe? need to build it locally to check
Avatar
hm didnt work
10:34
oh
10:34
app.config['MAX_CONTENT_LENGTH'] = 1024 * 128
10:35
can we smuggle a file maybe (edited)
Avatar
Dockerfile of web/archive_stat_viewer was still the old version, so I updated it to the new version. Sorry for all inconvenience.
10:55
no big deal > RUN mkdir -p ./archives && chmod a+rw ./archives he added this
Avatar
symlinks works lrwxrwxrwx 1 user user 42 Jul 8 18:11 lol -> ../../../../../../../web-apps/src/flag.txt root@132798a72f37:/web-apps/src/archives/c5edbd4a-a443-4168-a753-646496c05179/files# cat lol dummy{dummy}`
Avatar
locally?
Avatar
ye locally
11:13
we need to retrieve the flag somehow
11:13
i have an idea though
11:13
@app.get('/results/<archive_id>') def download_result(archive_id): if 'archives' not in session: session['archives'] = [] archive_id = Path(archive_id).name return send_file(UPLOAD_DIR / archive_id / 'result.json')
11:14
if we could smuggle "files" into archive <id> and name the symlink result.json
11:14
imma try that
11:14
need to try several encodings
11:15
pretty sure thats the sol
Avatar
@jayden wants to collaborate 🀝
Avatar
we either need to smuggle "file" into the result endpoint (somehow breaking Path from pathlib) or tarslip bypassing ".."
12:48
dont think it's a pathlib 0day though
Avatar
Will recheck tmrw
Avatar
have you checked the other one
13:30
actually lego is on it
Avatar
I saw it, does’t look fun
Avatar
yeah
Avatar
Another idea is memory exhuastion when uploading the tar. But that didnt work it crashes
Avatar
@irogir wants to collaborate 🀝
Avatar
@Legoclones wants to collaborate 🀝
Avatar
@DreyAnd wants to collaborate 🀝
Avatar
got an hour before i sleep, will try at least
15:58
tomorrow got A/D ctf onsite
Avatar
@rubiya wants to collaborate 🀝
Avatar
we can use symlinks to get the flag
02:00
but this would still require some traversal i think
02:01
so the only thing i noticed about Path() is that it will reset previous components if smth starts with /
Avatar
Avatar
irogir
but this would still require some traversal i think
well question is whether there exists another way to specify an absolute path
02:07
The Python programming language. Contribute to python/cpython development by creating an account on GitHub.
02:07
its using os.path.join too
Avatar
Avatar
irogir
so the only thing i noticed about Path() is that it will reset previous components if smth starts with /
Ye
Avatar
Avatar
irogir
its using os.path.join too
Ye but thats only useful if its starts with /
02:28
I am not sure how it will react to a path that starts with ~
02:29
Could you try it? I am not home rn
Avatar
@Guesslemonger wants to collaborate 🀝
Avatar
it wont expand
Avatar
its prob an edge case for paths
03:00
or 0day in pathlib
Avatar
Guesslemonger 07/09/2023 3:11 AM
is it even related to tar slip?
03:11
tar file doesn't matter since it is referenced by uuid after extraction and file names can't have dots and slashes
Avatar
there is not much functionality. we dont need to use .. or /, if we manage archive_id = Path(archive_id).name return send_file(UPLOAD_DIR / archive_id / 'result.json') to return smth from upload_dir/folder1/folder2/result.json
03:17
but imo its more likely that there is some kind of normalization/ bypass on the tar extract
Avatar
Guesslemonger 07/09/2023 3:19 AM
/folder1/folder2 ? it has only 1 layer of folder
Avatar
no the layout will be uuid/archive.tar uuid/result.json uuid/files/<unpacked files>
Avatar
Avatar
irogir
there is not much functionality. we dont need to use .. or /, if we manage archive_id = Path(archive_id).name return send_file(UPLOAD_DIR / archive_id / 'result.json') to return smth from upload_dir/folder1/folder2/result.json
also if we create a result.juson in the container and try /result/.. it will get it
Avatar
the app will serve upload_dir/dir1/result.json
Avatar
Guesslemonger 07/09/2023 3:22 AM
can we have unicode instead of normal dots?
Avatar
so if we can extract the symlink at least one dir and name it "result.json" we'll be able to extract it
Avatar
Guesslemonger 07/09/2023 3:22 AM
i think extractall normalizes it
Avatar
i already tried, doesnt normalize
Avatar
Avatar
Guesslemonger
can we have unicode instead of normal dots?
doesnt work
Avatar
Avatar
Guesslemonger
i think extractall normalizes it
ref?
Avatar
tried several encoding on results
Avatar
hmm sounds interesting
Avatar
thats from 2010
Avatar
doesnt sounds interesting
03:24
"last changed 2022-04-11 14:56 by admin"
Avatar
Complexity breeds vulnerability; optimization demands compensation: an exploration of a series of vulnerabilities reported against NPM.
Avatar
Guesslemonger 07/09/2023 3:32 AM
archive_id = Path(archive_id).name this is secure i think? else we could traverse
Avatar
i checked src and it splits at last occurence of "/". seems safe
Avatar
Guesslemonger 07/09/2023 3:34 AM
unicode here?
03:34
i tested with unicode slash, works
03:34
does send_file normalize unicode
Avatar
i dont remember seeing it normalizing stuff (both pathlib and werkzeug)
Avatar
Guesslemonger 07/09/2023 3:36 AM
wait, they are already putting in / after upload_dir
03:39
won't work anyway, since we won't have results.json in that case
Avatar
The comprehensive WSGI web application library. Contribute to pallets/werkzeug development by creating an account on GitHub.
Avatar
Avatar
Guesslemonger
archive_id = Path(archive_id).name this is secure i think? else we could traverse
we can only ".." but there be result.json there
Avatar
Avatar
ElleuchX1
also if we create a result.juson in the container and try /result/.. it will get it
like here*
Avatar
there is no point in trying to traverse there when we cant traverse via tar extraction
Avatar
ye exactly, just incase we somehow do that
Avatar
e.g. members that have absolute filenames starting with "/" or filenames with two dots "..".
03:47
well on nt \ would be normalized
03:49
could we do anything with arb delete maybe
Avatar
that didnt work
03:49
i've tried it
Avatar
what exactly
03:50
ok i just read that rmtree prevents against symlinks
Avatar
using "\"
03:50
to traverse
03:50
it doesnt normalize it
Avatar
Guesslemonger 07/09/2023 3:51 AM
any possibility of template injection via cookies?
Avatar
it has a different behavoir in windows though (it changed / to \ )
Avatar
oh lol can we just prepend with " "
03:51
the tar entry name
Avatar
Avatar
irogir
oh lol can we just prepend with " "
uh empty
Avatar
Avatar
Guesslemonger
any possibility of template injection via cookies?
we need the secret
Avatar
Avatar
irogir
oh lol can we just prepend with " "
no didnt work
Avatar
Guesslemonger 07/09/2023 3:52 AM
{{archive['name']}} can be used to dump config?
Avatar
its not vulnerable to ssti
Avatar
Guesslemonger 07/09/2023 3:52 AM
ok
Avatar
I was also trying to exhuast memory / race condition but it didnt work
03:53
import tarfile archive_path = 'large_archive.tar' extract_folder = 'extracted_files' with tarfile.open(archive_path, 'w') as archive: for i in range(250): file_name = f'file_{i}.txt' content = f'This is file {i}' nested_folder = '/'.join(['nested'] * 2) archive.addfile(tarfile.TarInfo(name=f'{nested_folder}/{file_name}'), content.encode()) filename = '../result.json' archive.addfile(tarfile.TarInfo(name=filename))
03:54
it pepegs a lot
Avatar
is this web or zip misc
Avatar
Avatar
sahuang
is this web or zip misc
tar misc
Avatar
Avatar
ElleuchX1
import tarfile archive_path = 'large_archive.tar' extract_folder = 'extracted_files' with tarfile.open(archive_path, 'w') as archive: for i in range(250): file_name = f'file_{i}.txt' content = f'This is file {i}' nested_folder = '/'.join(['nested'] * 2) archive.addfile(tarfile.TarInfo(name=f'{nested_folder}/{file_name}'), content.encode()) filename = '../result.json' archive.addfile(tarfile.TarInfo(name=filename))
what would this bring
Avatar
bruh
Avatar
i mean what you racing
Avatar
Avatar
irogir
i mean what you racing
the check, there was another challenge that had the same idea - just copied my poc script to test it
Avatar
Avatar
irogir
i mean what you racing
since there is the length limit, i thought maybe there is an edge case where it will error out that function
Avatar
i really dont feel like reading how they parse an archive
03:56
but i feel like its related to the check of member.name
03:56
because there are no other places i could think of which are vuln
Avatar
ye true
Avatar
Guesslemonger 07/09/2023 4:25 AM
even if we bypass that, there will not be any result.json to display?
Avatar
we could overwrite an existing result.json and read it this way
Avatar
Guesslemonger 07/09/2023 4:32 AM
what would we overwrite it with?
Avatar
a symlink to the flag
Avatar
Avatar
ElleuchX1
symlinks works lrwxrwxrwx 1 user user 42 Jul 8 18:11 lol -> ../../../../../../../web-apps/src/flag.txt root@132798a72f37:/web-apps/src/archives/c5edbd4a-a443-4168-a753-646496c05179/files# cat lol dummy{dummy}`
@Guesslemonger
Avatar
Guesslemonger 07/09/2023 4:45 AM
why is it 404?
04:45
path.name should still extract uuid and send result.json
Avatar
cus its considering it as ../ as the path in the http request
04:46
just put ".." and check the container logs u'll get an error
Avatar
Guesslemonger 07/09/2023 4:46 AM
isn't it considering entire thing after first slash as archive id?
Avatar
also Path().name removes the /
Avatar
no, but it would if archive_id was declared as a path
04:48
like this, it just wont find a matching route. so we cant use /
Avatar
Guesslemonger 07/09/2023 4:48 AM
then we can't really exploit name
Avatar
unless some encoding works, yes
Avatar
what encoding
Avatar
but tried mutiple encodings
Avatar
there is no normalization happening, see src
Avatar
idk i just copy pasted a wordlist
Avatar
Guesslemonger 07/09/2023 4:49 AM
still feel leaking secret might be the way
04:49
also is clean just a quality of life thing lol
Avatar
Avatar
Guesslemonger
still feel leaking secret might be the way
well then, leaking the flag would be eaiser x)
Avatar
Avatar
Guesslemonger
also is clean just a quality of life thing lol
i also thought about it being there for exploitation, but it feels like a deadend
04:50
the only thing i havent checked completely is how that tar parsing is done in detail
04:50
everything else seems safe and also pretty default
Avatar
Guesslemonger 07/09/2023 4:51 AM
hmm so tar slip without dots in general
Avatar
vuln is very likely that the blacklist is not complete
Avatar
ye for sure
04:52
i've tried .\ ./ ~/ for now
Avatar
Guesslemonger 07/09/2023 4:52 AM
def extractall(self, path=".", members=None, *, numeric_owner=False, filter=None): """Extract all members from the archive to the current working directory and set owner, modification time and permissions on directories afterwards. `path' specifies a different directory to extract to. `members' is optional and must be a subset of the list returned by getmembers(). If `numeric_owner` is True, only the numbers for user/group names are used and not the names. The `filter` function will be called on each member just before extraction. It can return a changed TarInfo or None to skip the member. String names of common filters are accepted. """ directories = [] filter_function = self._get_filter_function(filter) if members is None: members = self for member in members: tarinfo = self._get_extract_tarinfo(member, filter_function, path) if tarinfo is None: continue if tarinfo.isdir(): # For directories, delay setting attributes until later, # since permissions can interfere with extraction and # extracting contents can reset mtime. directories.append(tarinfo) self._extract_one(tarinfo, path, set_attrs=not tarinfo.isdir(), numeric_owner=numeric_owner) # Reverse sort directories. directories.sort(key=lambda a: a.name, reverse=True) # Set correct owner, mtime and filemode on directories. for tarinfo in directories: dirpath = os.path.join(path, tarinfo.name) try: self.chown(tarinfo, dirpath, numeric_owner=numeric_owner) self.utime(tarinfo, dirpath) self.chmod(tarinfo, dirpath) except ExtractError as e: self._handle_nonfatal_error(e)
Avatar
os.path.join is the vuln part
Avatar
Guesslemonger 07/09/2023 4:53 AM
right, so exploit it with that blacklist hmm
04:55
https://ctftime.org/writeup/22408 this won't work because of slash check in beginning i think?
Avatar
ye
Avatar
Guesslemonger 07/09/2023 5:01 AM
since it is not a docker container, if we have directory traversal permissions, can't we break the chall?
05:01
they might not have granted write permission in app folder hmm
Avatar
it breaks it and the container resets
05:03
oonly when giving a large file
Avatar
4 solves hm
Avatar
ok i think i got it
Avatar
crew{fixing_zip/tar_slip_vulnerability_is_hard}
πŸ”₯ 3
Avatar
Broooooooo
07:19
Too strong
Avatar
import sys, zipfile, tarfile, os, optparse def e(a): with tarfile.open(a) as archive: for m in archive.getmembers(): print(m.name) tf = tarfile.open("out.tar", "w") l = tarfile.TarInfo("x") l.linkname = "../re" # tf.addfile(l, open("x", "r")) tf.add("x", "symdir") tf.add("flag.txt", "symdir/result.json") # tf.add("old.tar", "old.tar") # tf.add("test") tf.close() e("out.tar") (edited)
07:20
where ln -sf /web-apps/src/archives/<existing uuid> x ln -sf /web-apps/src/flag.txt flag.txt
07:21
so basically putting a symlink in a symlinked directory
Avatar
Avatar
irogir
used /ctf solve
βœ… Challenge solved.
Avatar
damn, strong
07:21
gg
Avatar
zip slip vuln?
07:22
first time heard about it, need a read
Avatar
clever
Avatar
is this vuln in old lib only?
07:28
like does the chal have some old dependency on project using zip with slip vuln
07:28
im just wondering how its fixed in latest products
Avatar
its using a pretty new python version. i went through some issues, and they keep saying that devs are responsible to sanitize stuff in archives
Avatar
Guesslemonger 07/09/2023 7:43 AM
i don't understand even after seeing solve πŸ˜„ no skills
Exported 179 message(s)